// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#include "rotor-light/planner.hpp"
#include <cassert>

using namespace rotor_light;

TimeEvent &TimeEvent::operator=(const TimeEvent &other) {
  callback = other.callback;
  data = other.data;
  when = other.when;
  return *this;
}

PlannerBase::PlannerBase(TimeEvent *events_, uint32_t events_count_)
    : events{events_}, events_count{events_count_}, last_event{-1} {}

bool PlannerBase::add_event(TimePoint when, Callback callback, void *data) {
  if (last_event + 1 < events_count) {
    int i = 0;
    // find the insertion point (skip previous events)
    for (; i <= last_event; ++i) {
      auto &event = events[i];
      if (event.when <= when) {
        continue;
      }
      break;
    }
    // copy next events into the next position
    for (int j = last_event; j >= i; --j) {
      events[j + 1] = events[j];
    }
    events[i] = TimeEvent{callback, data, when};
    ++last_event;
    return true;
  }
  return false;
}

void PlannerBase::remove_event(TimePoint when, void *data) {
  int from = -1;
  int till = -1;
  for (int i = 0; i <= last_event; ++i) {
    auto &e = events[i];
    if (e.when == when && e.data == data) {
      if (from == -1) {
        from = i;
      }
      till = i;
    }
  }
  if (till >= 0) {
    auto delta = 1 + till - from;
    for (int i = from; (i <= till) && (i + delta) <= last_event; ++i) {
      events[i] = events[i + delta];
    }
    last_event -= delta;
  }
}

EventConsumer PlannerBase::consume(TimePoint deadline) {
  return EventConsumer(deadline, this);
}

void PlannerBase::commit(size_t index) {
  assert(index <= last_event);
  for (int i = index + 1; i <= last_event; ++i) {
    events[i - (index + 1)] = events[i];
  }
  last_event -= (index + 1);
}

TimePoint PlannerBase::next_event() {
  if (last_event >= 0) {
    return events[0].when;
  }
  return 0;
}

EventConsumer::EventConsumer(TimePoint deadline_, PlannerBase *watchdog_)
    : deadline{deadline_}, watchdog{watchdog_}, index{0} {}

EventConsumer::operator bool() const {
  return (index <= watchdog->last_event) &&
         (watchdog->events[index].when <= deadline);
}

void EventConsumer::call_next() {
  auto &event = watchdog->events[index++];
  event.callback(event.data);
}

EventConsumer::~EventConsumer() {
  if (--index >= 0) {
    watchdog->commit(index);
  }
}
